# -*- coding: utf-8 -*-
import re
import pandas as pd
import xml.etree.ElementTree as ET

from get_cwe_root import get_cwe_root

from JULIET import JULIET
from PERFORMANCE import PERFORMANCE

class SPOTBUGS:
    '''对 spotbugs 的 result.xml 进行分析'''
    def __init__(self) -> None:
        # spotbugs 结果 xml 文件路径
        self.spotbugs_result_xml_path='../scan_data/spotbugs/spotbugs-scan-result.xml'
        # spotbugs 的结果与 cwe 编号对应关系的 excel 文件路径
        self.juliet_cwe_and_spotbugs_excel_path='./resource/spotbugs测试结果与juliet的CWE编号对应关系.xlsx'
        # spotbugs 的结果与 cwe 编号对应关系的 excel 文件的 sheet 表名
        self.juliet_cwe_and_spotbugs_excel_sheetname='cwe'
        # 根据 juliet_cwe_and_spotbugs_excel_path 来生成的，将 bug_type 与 cweid 对应
        self.bug_type_to_cweid_dict={}
        # 根据 spotbugs_result_xml_path 来生成的，保存了 spotbugs 的分析结果
        self.result=[]
        # 生成 result 数据
        self.analyze_result=[]
        # 用于获取 cwe 编号之间继承关系的对象
        self.cwe_root=get_cwe_root()
        # juliet 对象，里面包括了 juliet 数据集的预期数据及一些可以调用的方法
        self.juliet=JULIET()
        self.get_result_from_xml_file()
        self.get_bug_type_cweid_dict()
        self.get_analyze_result()
        # # 性能分析
        # time_log_path='/home/pudding/work/temp/result/sast_result/sast_result/spotbugs/3/spotbugs_time.log'
        # top_log_path='/home/pudding/work/temp/result/sast_result/sast_result/spotbugs/3/spotbugs_top.log'
        # iotop_log_path='/home/pudding/work/temp/result/sast_result/sast_result/spotbugs/3/spotbugs_iotop.log'
        # top_command='spotbugs'
        # iotop_command='spotbugs run --bufferoverrun --biabduction --pulse -- make -j6 -B'
        # performance_save_path='spotbugs_performance.xlsx'
        # performance=PERFORMANCE(time_log_path,top_log_path,iotop_log_path,top_command,iotop_command,performance_save_path)

    def get_bug_type_cweid_dict(self):
        '''将 bug_type 字段解析为 cweid'''
        df=pd.read_excel(self.juliet_cwe_and_spotbugs_excel_path,sheet_name=self.juliet_cwe_and_spotbugs_excel_sheetname)
        for i in range(len(df['cweid'])):
            cweid=df['cweid'][i]
            bug_types=df['bug_types'][i]
            if pd.isnull(bug_types):
                continue
            for bug_type in bug_types.split(' '):
                if not self.bug_type_to_cweid_dict.get(bug_type):
                    self.bug_type_to_cweid_dict[bug_type]=cweid

    def get_result_from_xml_file(self):
        '''将 spotbugs 分析出的 json 结果解析到 result 对象中'''
        tree=ET.parse(self.spotbugs_result_xml_path)
        root=tree.getroot()
        bug_instances=root.findall('BugInstance')

        for bug_instance in bug_instances:
            bug_type=bug_instance.attrib['type']

            line=-1
            source_line=bug_instance.find('SourceLine')
            if source_line is not None:
                # 一般漏洞，报在具体代码行
                line=source_line.attrib['start']
            elif 'start' in bug_instance.find('Class').find('SourceLine').attrib:
                # 漏洞报在类上
                line=bug_instance.find('Class').find('SourceLine').attrib['start']
            elif bug_instance.find('Field') is not None:
                # 漏洞报在成员变量上
                line=0
            elif bug_instance.find('Method') is not None:
                # 漏洞报在函数上
                source_line=bug_instance.find('Method').find('SourceLine')
                if source_line is not None:
                    line=source_line.attrib['start']

            method_name=''
            if bug_instance.find('Method') is not None:
                method_name=bug_instance.find('Method').attrib['name']

            one_result={
                        'bug_type':bug_instance.attrib['type'],
                        'class_name':bug_instance.find('Class').attrib['classname'],
                        'method_name':method_name,
                        'line':str(line),
                    }
            self.result.append(one_result)

    def get_analyze_result(self):
        '''对结果进行分析'''
        # juliet 测试用例的总数
        juliet_testcases_number=0
        for item in self.juliet.result.values():
            juliet_testcases_number+=len(item)
        # spotbugs 重复报告相同的 juliet 测试用例数量
        spotbugs_repeat_number=0
        # spotbugs 的结果总数
        spotbugs_result_number=len(self.result)
        # 匹配成功的用例数量
        success_report_number=0
        # 匹配成功的 juliet 的测试用例列表,避免同一个测试用例被多次算作 success_report_number 里
        success_juliet_testcase_list=[]
        # 遍历 spotbugs 的 result
        for item in self.result:
            # 如果是与 juliet 测试用例中的 cwe 编号匹配的 bug_type
            if item['bug_type'] in self.bug_type_to_cweid_dict.keys():
                cweid=self.bug_type_to_cweid_dict[item['bug_type']]
                file_name=item['class_name'].split('.')[-1]+'.java'
                # 根据文件名对应到 juliet 里的一个测试用例
                juliet_testcase=self.juliet.get_testcase_by_file_name(file_name)
                # 如果找不到该文件名
                if not juliet_testcase:
                    continue
                # 获取 juliet 的测试用例对应的 cweid
                juliet_testcase_cweid=self.juliet.get_testcase_cweid(juliet_testcase)
                # 获取 spotbugs 的 cweid 与 juliet 的 cweid 之间的继承关系
                relations=self.cwe_root.get_relations(cweid,juliet_testcase_cweid)
                # spotbugs 的 cweid 为 juliet 的祖辈或者相等或子辈
                if relations in ['ancestor','equal','descendant']:
                    # 如果行数匹配,判断为一次成功报告,并记录到 success_juliet_testcase_list 中防止后续被重复计算
                    if self.juliet.if_mate_line(item['line'],file_name,juliet_testcase):
                        # 判断该 juliet 的测试用例如果之前有没有被统计过
                        if juliet_testcase not in success_juliet_testcase_list:
                            success_report_number+=1
                            success_juliet_testcase_list.append(juliet_testcase)
                            continue
                        else:
                            spotbugs_repeat_number+=1
                    # 行数不匹配的情况下,判断当前报告的函数是否为 bad 函数
                    elif self.juliet.if_bad_function_name(item['method_name']):
                        # 判断该 juliet 的测试用例如果之前有没有被统计过
                        if juliet_testcase not in success_juliet_testcase_list:
                            success_report_number+=1
                            success_juliet_testcase_list.append(juliet_testcase)
                            continue
                        else:
                            spotbugs_repeat_number+=1

        # 召回率/准确率：正确报告数与测试用例中用例总数的比值
        rcr=success_report_number/juliet_testcases_number
        # 误报率：错误报告数占工具所有报告总数的比值，错误报告数=spotbugs总报告数-spotbugs成功报告数-spotbugs重复报告数
        fpr=(spotbugs_result_number-success_report_number-spotbugs_repeat_number)/spotbugs_result_number
        excel_res={
            'juliet_testcases_number':[juliet_testcases_number],
            'spotbugs_result_number':[spotbugs_result_number],
            'success_report_number':[success_report_number],
            'rcr':[rcr],
            'fpr':[fpr],
        }
        df=pd.DataFrame(excel_res)
        df.to_excel('./analyze_result/spotbugs_analyze_result.xlsx', sheet_name='main')